/* eslint-disable no-inner-declarations */
import React, {
  useRef,
  useState,
  useMemo,
} from 'react'
import runes from 'runes2'
import type {
  EllipsisProps,
  EllipsisedValue,
} from './type'
import { useIsomorphicLayoutEffect } from '../../hooks/use-isomorphic-layout-effect'
import { withStopPropagation } from '../../utils/with-stop-propagation'
import { withNativeProps } from '../../utils/native-props'
import { useResizeEffect } from '../../hooks/use-resize-effect'

const classPrefix = 'rt-ellipsis'

function pxToNumber(value: string | null): number {
  if (!value) return 0
  const match = value.match(/^\d*(\.\d*)?/)
  return match ? Number(match[0]) : 0
}

export const Ellipsis: React.FC<EllipsisProps> = (props) => {
  const {
    content = '',
    collapseText = '',
    defaultExpanded = false,
    direction = 'end',
    expandText = '',
    onContentClick = () => { /** */ },
    rows = 1,
    stopPropagationForActionButtons = ['click'],
  } = props

  const rootRef = useRef<HTMLDivElement>(null)
  const expandElRef = useRef<HTMLAnchorElement>(null)
  const collapseElRef = useRef<HTMLAnchorElement>(null)

  const [ellipsised, setEllipsised] = useState<EllipsisedValue>({})
  const [exceeded, setExceeded] = useState<boolean>(false)
  const [expanded, setExpanded] = useState<boolean>(defaultExpanded)

  const chars = useMemo(() => runes(content), [content])

  function getSubString(start: number, end: number) {
    return chars.slice(start, end).join('')
  }

  const calcEllipsised = () => {
    const root = rootRef.current
    if (!root) return

    const originDisplay = root.style.display
    root.style.display = 'block'
    const originStyle = window.getComputedStyle(root)
    const container = document.createElement('div')
    const styleNames: string[] = Array.prototype.slice.apply(originStyle)
    styleNames.forEach((name) => {
      container.style.setProperty(name, originStyle.getPropertyValue(name))
    })
    root.style.display = originDisplay

    container.style.height = 'auto'
    container.style.minHeight = 'auto'
    container.style.maxHeight = 'auto'
    container.style.textOverflow = 'clip'
    container.style.webkitLineClamp = 'unset'
    container.style.display = 'block'
    const lineHeight = pxToNumber(originStyle.lineHeight)
    const maxHeight = Math.floor(
      lineHeight * rows
      + pxToNumber(originStyle.paddingTop)
      + pxToNumber(originStyle.paddingBottom),
    )
    container.innerText = content
    document.body.appendChild(container)

    /**
     * 如果container高度小于rootRef最大高度 正常展示
     * 如果container高度大于rootRef最大高度 实行文本省略
     */
    if (container.offsetHeight <= maxHeight) {
      setExceeded(false)
    } else {
      setExceeded(true)

      const end = content.length
      const collapseEl = typeof collapseText === 'string' ? collapseText : collapseElRef.current?.innerHTML
      const expandEl = typeof expandText === 'string' ? expandText : expandElRef.current?.innerHTML
      const actionText = expanded ? collapseEl : expandEl

      function check(left: number, right: number): EllipsisedValue {
        if (right - left <= 1) {
          if (direction === 'end') {
            return {
              leading: `${getSubString(0, left)}...`,
            }
          }
          return {
            tailing: `...${getSubString(right, end)}`,
          }
        }

        const middle = Math.round((left + right) / 2)
        if (direction === 'end') {
          container.innerHTML = `${getSubString(0, middle)}...${actionText}`
        } else {
          container.innerHTML = `${actionText}...${getSubString(middle, end)}`
        }

        if (container.offsetHeight <= maxHeight) {
          if (direction === 'end') {
            return check(middle, right)
          }
          return check(left, middle)
        }
        if (direction === 'end') {
          return check(left, middle)
        }
        return check(middle, right)
      }

      function checkMiddle(
        leftPart: [number, number],
        rightPart: [number, number],
      ): EllipsisedValue {
        if (
          leftPart[1] - leftPart[0] <= 1
          && rightPart[1] - rightPart[0] <= 1
        ) {
          return {
            leading: `${getSubString(0, leftPart[0])}...`,
            tailing: `...${getSubString(rightPart[1], end)}`,
          }
        }
        const leftPartMiddle = Math.floor((leftPart[0] + leftPart[1]) / 2)
        const rightPartMiddle = Math.ceil((rightPart[0] + rightPart[1]) / 2)
        container.innerHTML = `${getSubString(0, leftPartMiddle)}...${actionText}...${getSubString(rightPartMiddle, end)}`
        if (container.offsetHeight <= maxHeight) {
          return checkMiddle(
            [leftPartMiddle, leftPart[1]],
            [rightPart[0], rightPartMiddle],
          )
        }
        return checkMiddle(
          [leftPart[0], leftPartMiddle],
          [rightPartMiddle, rightPart[1]],
        )
      }

      const middle = Math.floor((0 + end) / 2)
      const ell = direction === 'middle'
        ? checkMiddle([0, middle], [middle, end])
        : check(0, end)
      setEllipsised(ell)
    }

    document.body.removeChild(container)
  }

  useResizeEffect(calcEllipsised, rootRef)

  useIsomorphicLayoutEffect(() => {
    calcEllipsised()
  }, [
    content,
    direction,
    rows,
    expandText,
    collapseText,
  ])
  // 展开文案
  const expandActionElement = !!props.expandText
    && withStopPropagation(
      stopPropagationForActionButtons,
      <a
        href="#!"
        role="button"
        ref={expandElRef}
        onClick={() => {
          setExpanded(true)
        }}
      >
        {expandText}
      </a>,
    )
  // 收起文案
  const collapseActionElement = !!props.collapseText
    && withStopPropagation(
      stopPropagationForActionButtons,
      <a
        href="#!"
        role="button"
        ref={collapseElRef}
        onClick={() => {
          setExpanded(false)
        }}
      >
        {collapseText}
      </a>,
    )

  const renderContent = () => {
    // 正常展示
    if (!exceeded) return content

    // 已展开 则正常展示并展示收起按钮
    if (expanded) {
      return (
        <>
          {content}
          {collapseActionElement}
        </>
      )
    }
    // 未展开，根据position展示省略内容
    return (
      <>
        {ellipsised.leading}
        {expandActionElement}
        {ellipsised.tailing}
      </>
    )
  }

  return withNativeProps(
    props,
    <div
      ref={rootRef}
      className={classPrefix}
      onClick={(e) => {
        if (e.target === e.currentTarget) {
          onContentClick(e)
        }
      }}
    >
      {renderContent()}
    </div>,
  )
}
